# Copyright (C) 2005-2006 Frederic Back <fredericback@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.

import gtk
import gobject
import gc

from _clipboard import GeoClipboard
from _options import Options
from _canvas import MapCanvas
from _cursors import *
from GeoTypes import Point
from _guitools import errorMessage

#---------------------------------------------------------------------------------------------------
def openMapView(resultset):
    """ open a new window and show a layer's table """

    # if a tableWindow with layer is already open, focus on it
    for t in MapWindow.instances:
        if t.resultset == resultset: 
            t.present()
            return t
    m = MapWindow(resultset)
    return m

def getMapView(layer):
    for mw in MapWindow.instances:
        for l in mw.resultset.layers:  
            if l == layer:
                return mw

class MapWindow(gtk.Window):
    """ Show one or more layers in a window """
    instances = [] # list of open MapWindows

    def __onAccel(self, accel_group, acceleratable, keyval, modifier):
        if keyval == ord('w') or keyval == ord('q'): self.close()
        elif keyval == ord('+'): self.canvas.updateZoom(2.0)
        elif keyval == ord('-'): self.canvas.updateZoom(0.5)

    def __init__(self, resultset):
        MapWindow.instances.append(self)
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        width, height = 480, 320

        self.resultset = resultset
        resultset.connect("layer-removed",lambda r,l: self.refreshMap())
        resultset.connect("layer-added",lambda r,l: self.refreshMap())
        resultset.connect("values-changed",lambda r: self.set_title(r.name))
        resultset.connect("removed", self.close)

        self.set_title(resultset.name)
        self.connect("delete_event", self.close)

        # create an accelgroup
        ag = gtk.AccelGroup()
        ag.connect_group(ord('w'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.__onAccel)
        ag.connect_group(ord('q'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.__onAccel)
        ag.connect_group(ord('+'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.__onAccel)
        ag.connect_group(ord('-'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.__onAccel)
        self.add_accel_group(ag)

        # ------------------------------------------------------- create toolbar
        self.toolbar = gtk.Toolbar()

        self.toolbar.pan = b = gtk.RadioToolButton(None,None)
        self.toolbar.insert(b,0)
        img = gtk.Image()
        pm = gtk.gdk.pixmap_create_from_xpm_d(gtk.gdk.Pixmap(None,16,16,24),None,
            CursorCollection.xpm["hand-open"])
        img.set_from_pixmap(*pm)
        b.set_icon_widget(img)
        b.show()
        b.connect("toggled", self.toolButtonChanged)

        self.toolbar.zoomToExtent = b = gtk.RadioToolButton(self.toolbar.pan,gtk.STOCK_ZOOM_IN)
        b.connect("clicked", self.toolButtonChanged)
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        b = gtk.ToolButton(gtk.STOCK_ZOOM_FIT)
        b.connect("clicked", lambda w: self.canvas.resetZoom() )
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        b = gtk.SeparatorToolItem() #-------------------------------------------
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        b = gtk.ToolButton(gtk.STOCK_REFRESH)
        b.connect("clicked", lambda w: self.refreshMap())
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        b = gtk.ToolButton(gtk.STOCK_SAVE_AS)
        b.connect("clicked", self.saveImage)
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        b = gtk.SeparatorToolItem() #-------------------------------------------
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        self.toolbar.point = b = gtk.RadioToolButton(self.toolbar.pan,None)
        img = gtk.Image()
        img.set_from_file(Options.iconpath+"icon_pointselect.png")
        b.set_icon_widget(img)
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        self.toolbar.box = b = gtk.RadioToolButton(self.toolbar.pan,None)
        img = gtk.Image()
        img.set_from_file(Options.iconpath+"icon_boxselect.png")
        b.set_icon_widget(img)
        b.connect("toggled", self.toolButtonChanged)
        self.toolbar.insert(b,self.toolbar.get_n_items())
        b.show()

        #b = gtk.ToolButton(None)
        #img = gtk.Image()
        #img.set_from_file(Options.iconpath+"icon_lock_extent.png")
        #b.set_icon_widget(img)
        #self.toolbar.insert(b,-1)
        #b.show()

        #----------------------------------------------------------------
        # realize and show
        vbox = gtk.VBox()
        self.canvas = MapCanvas()
        
        # callback methods
        self.canvas.connect( "point-selected", self.onPointSelected )
        self.canvas.connect( "box-selected", self.onBoxSelected )


        vbox.pack_start(self.toolbar,False)
        vbox.pack_start(self.canvas)
        self.add(vbox)

        # Note: With set_default_size, the image can't be downsized.
        self.set_size_request(100,100) # minimum size, needed!
        self.resize(width,height)
        self.realize()
        self.show_all()
        self.canvas.setCurrentTool("pan") # 'pan' is hardcoded

        self.refreshMap()
        extent = self.resultset.viewExtent 
        try:
            if extent: self.canvas.zoomToExtent(*extent)
        except:
            print "Unable to zoom to extent:",extent
            self.resultset.viewExtent = None
        self.canvas.connect( "view-changed", self.onViewChanged ) # connect only now

    #---------------------------------------------------------------------------
    def onPointSelected(self, widget, x, y):
        if self.toolbar.zoomToExtent.get_active(): # zoom out on right click
            self.canvas.zoomFrom(x,y,0.5)
        else:
            try: s = Options.pointSyntax%{"x":x,"y":y}
            except Exception, e:
                errorMessage("Unable to insert co-ordinates into template string",Options.boxSyntax,self)
            GeoClipboard.instance.addElement(self,s)

    def onBoxSelected(self, widget, x1, y1, x2, y2):
        if self.toolbar.zoomToExtent.get_active(): # zoom on selection
            self.canvas.zoomToExtent(x1, y1, x2, y2)
        else:
            try: s = Options.boxSyntax%{"x1":x1,"y1":y1,"x2":x2,"y2":y2}
            except Exception, e:
                errorMessage("Unable to insert co-ordinates into template string",Options.boxSyntax,self)
            GeoClipboard.instance.addElement(self,s)
    #---------------------------------------------------------------------------

    def toolButtonChanged(self, widget):
        if self.toolbar.pan.get_active(): self.canvas.setCurrentTool("pan")
        elif self.toolbar.box.get_active(): self.canvas.setCurrentTool("box")
        elif self.toolbar.zoomToExtent.get_active(): self.canvas.setCurrentTool("box")
        else: self.canvas.setCurrentTool("pointer")


    def saveImage(self, widget=None):
        d = gtk.FileChooserDialog(_("Save As..."),
            self.get_toplevel(), gtk.FILE_CHOOSER_ACTION_SAVE,
            (gtk.STOCK_CANCEL,0,gtk.STOCK_OK,1), None)
        d.set_current_name( self.resultset.name.replace(" ","_") + ".png" )
        r = d.run()
        if r == 1:
            #print d.get_uri()
            pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,self.canvas.width,self.canvas.height)
            pixmap, mask = self.canvas.image.get_pixmap()
            pixbuf.get_from_drawable(pixmap,pixmap.get_colormap(),0,0,0,0,-1,-1)
            #pixbuf.save(d.get_filename(), "jpeg", {"quality":"100"})
            pixbuf.save(d.get_filename(), "png", {})
        d.destroy()


    def refreshMap(self,widet=None):
        if len(self.resultset.layers) == 0:
            self.close()
            return

        for layer in self.resultset.layers:
            layer.connect("style-changed",self.refreshMap)
            layer.connect("source-reset",self.refreshMap)

        self.canvas.setLayers( self.resultset.layers )
    
    def onViewChanged(self,widget):
        extent = self.canvas.getCurrentViewExtent()
        #print "view changed:",extent
        self.resultset.viewExtent = extent

    def close(self,w=None,e=None):
        self.canvas.stop()
        gc.collect()
        if self in MapWindow.instances: MapWindow.instances.remove(self)
        self.destroy()
        print "removed mapwindow %s" %self


#---------------------------------------------------------------------------------------------------
def openTableView(layer):
    """ open a new window and show a layer's table. """
    # if a tableWindow with layer is already open, focus on it
    for t in TableWindow.instances:
        if t.layer == layer: 
            t.present()
            return t
    t = TableWindow(layer)
    #t.add_accel_group(self.accelgroup)
    return t

def getTableView(layer):
    for t in TableWindow.instances:
        if t.layer == layer:
            return t

class TableWindow(gtk.Window):
    """ Display a layer's table in a window 
        There is a 1:1 relation between a layer and a tablewindow """
    instances = [] # list of open TableWindows

    def __onAccel(self, accel_group, acceleratable, keyval, modifier):
        if   keyval == ord('Q'): self.close()

    def __init__(self, layer):
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        self.connect("delete_event", self.close)
        self.vbox = gtk.VBox()

        layer.connect("values-changed",self.refreshTitle)
        layer.connect("source-reset",self.refresh)

        # create an accelgroup
        ag = gtk.AccelGroup()
        ag.connect_group(ord('Q'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.__onAccel)
        self.add_accel_group(ag)

        #--- toolbar
        self.toolbar = gtk.Toolbar()
        self.vbox.pack_start(self.toolbar,False)
        
        #b = gtk.ToolButton(gtk.STOCK_REFRESH)
        #b.connect("clicked", lambda w: self.refresh())
        #self.toolbar.insert(b,self.toolbar.get_n_items())
        #b.show()       

        #--- central frame to place a widget
        self.content = gtk.Frame()
        self.content.set_shadow_type(gtk.SHADOW_NONE)
        self.vbox.pack_start(self.content)

        #--- statusbar
        self.statusbar = gtk.Statusbar()
        self.statusbar.set_has_resize_grip(True)
        self.statusbar.sid = self.statusbar.get_context_id("info")
        self.vbox.pack_end(self.statusbar,False)

        self.vbox.show_all()
        self.add(self.vbox)

        TableWindow.instances.append(self)

        self.layer = layer
        self.set_title("%s: %s" %(layer.name,layer.query))
        self.sw = gtk.ScrolledWindow()
        self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.sw.set_shadow_type(gtk.SHADOW_IN)
        self.content.add(self.sw)
        
        self.tv = None
        self.recreateTreeview()

        # finally, allocate size: not too big, not too small
        width, height = 480, 320
        #rect = self.tv.get_allocation()
        #if rect.width < 640: width = rect.width
        #if rect.height < 480: height = rect.height
        self.set_default_size(width,height)
        self.show()
        
    def set_status(self,msg):
        self.statusbar.pop(self.statusbar.sid)
        self.statusbar.push(self.statusbar.sid,str(msg))

    def refreshTitle(self,layer,status=None):
        self.set_title("%s"%layer.name)

    def refresh(self,w=None):
        self.recreateTreeview()

    def close(self,w=None,e=None):
        #if self.layer.resultset is None:
            #self.layer.shutdown()
                
        gc.collect()                
                
        TableWindow.instances.remove(self)
        self.tv.destroy()
        self.destroy()
        print "removed tablewindow %s" %self

    def recreateTreeview(self):
        """ Create a new treeview to show the layer """

        if self.tv is not None: self.tv.destroy()
        
        model = LayerTreeModel(self.layer, self.set_status)
        
        #model = gtk.TreeModelSort( model )
        
        self.tv = gtk.TreeView( model )
        self.tv.connect("button-press-event",self.__context_menu)
        self.tv.connect("row-activated",self.show_row_details )
        self.sw.add( self.tv )
        self.sw.show_all()

        if self.layer.queryDescription is None: return
        des = self.layer.queryDescription
        col = [None] * len(des)
        cell = [None] * len(des)
        for i in range(len(des)):
            cell[i] = gtk.CellRendererText()
            # replace '_' by '__' to avoid mnemonics
            col[i] = gtk.TreeViewColumn(des[i][0].replace("_",'__') ,cell[i])
            col[i].set_attributes(cell[i], text=i)
            #col[i].set_sort_column_id(i)
            col[i].set_resizable(True)
            col[i].set_reorderable(True)
            self.tv.append_column(col[i])
            if i in self.layer.geomCols:
                cell[i].set_property("style","PANGO_STYLE_ITALIC")
            else:
                cell[i].set_property("editable",True)
            #cell[i].set_property("width-chars",30)         

    def __context_menu(self, treeview, event):
        if event.button == 3:
            x = int(event.x)
            y = int(event.y)
            time = event.time
            pthinfo = treeview.get_path_at_pos(x, y)
            if pthinfo is not None:
                path, col, cellx, celly = pthinfo
                treeview.grab_focus()
                treeview.set_cursor( path, col, 0)
                print event.button
                
                menu = gtk.Menu()
                m = gtk.MenuItem(_("Show Row Details")); menu.append(m); m.show()
                m.connect("activate", self.show_row_details, path)   
                menu.popup( None, None, None, event.button, event.time)
            return 1

    def show_row_details(self, widget, path, column=None):
    
        dialog = gtk.Dialog(_("Row Details"), self.get_toplevel(), 
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            (gtk.STOCK_CLOSE,0))
            
        dialog.set_default_size(300,300)
        vbox = gtk.VBox()
        sw = gtk.ScrolledWindow()
        sw.add_with_viewport(vbox)
        sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
        dialog.vbox.add(sw)
    
        model = self.tv.get_model()
        it = model.get_iter(path)
        for i in range(model.get_n_columns()):
            b = gtk.VBox()
            vbox.pack_start(b)
            b.set_border_width(6)
            b.pack_start( gtk.Label(self.layer.queryDescription[i][0]), False )
            
            if i in self.layer.geomCols:
                t = gtk.TextView()
                t.set_wrap_mode(gtk.WRAP_WORD)
                v = self.layer.get_data( path[0], i )
                t.get_buffer().set_text( str(v) )    
                sw = gtk.ScrolledWindow()
                sw.set_shadow_type(gtk.SHADOW_IN)
                sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
                sw.add(t)
                b.pack_start(sw)
            else:
                e = gtk.Entry()
                v = self.layer.get_data( path[0], i )
                e.set_text( str(v) )
                b.pack_start(e)
                
        dialog.show_all()
        dialog.run()
        dialog.destroy()
        
#-------------------------------------------------------------------------------
class LayerTreeModel(gtk.GenericTreeModel):
    """ This class implements the treeModel interface to display Layer data
        in a table. """

    def __init__(self, layer, size_callback = None):
        """
        size_callback: function(message)
                       gets called when the number of rows changed                 
        """
        gtk.GenericTreeModel.__init__(self)
        #gtk.TreeSortable.__init__(self)
        self.layer = layer
        layer.connect("status-changed", self.status_changed)
        self.rows_displayed = 0
        self.size_callback = size_callback

        if self.size_callback:
            self.size_callback( _("%i rows")%self.on_iter_n_children(None) )
        
    def status_changed(self, layer=None, status=None):
        c = self.on_iter_n_children(None)
        if c == 0: return
        while self.rows_displayed < c-1:
            path = (self.rows_displayed,)
            #print "display",path
            self.row_inserted( path, self.get_iter(path) )
            #self.row_changed( path, self.get_iter(path) )
            self.rows_displayed += 1
            
        if self.size_callback:
            self.size_callback( _("%i rows")%self.on_iter_n_children(None) )
           
    def on_get_flags(self):
        return 0
        
    def on_get_n_columns(self):
        return len( self.layer.queryDescription )
        
    def on_get_column_type(self, index):
        return gobject.TYPE_STRING
        
    def on_get_path(self, node):
        return node
        
    def on_get_iter(self, path):
        return path
       
        
    def on_get_value(self, node, column):
        self.layer.lock.acquire()
        if column in self.layer.geomCols:
            value = "geometry"
        else:
            try: value = self.layer.data[node[0]][column]
            except: value = None
        self.layer.lock.release()    
        return value
        
    def on_iter_next(self, node):
        if node[0]+1 < self.on_iter_n_children(None): return (node[0]+1,)
        return None
        
    def on_iter_children(self, node):
        return None
        
    def on_iter_has_child(self, node):
        return False
        
    def on_iter_n_children(self, node):
        if node is None:
            self.layer.lock.acquire()
            n = len(self.layer.data)
            self.layer.lock.release()
            return n   
        return 0
            
    def on_iter_nth_child(self, node, n):
        if node == None: return(n,)
        return None
            
    def on_iter_parent(self, node):
        return None
